package main

import (
	"encoding/base64"
	"golang.org/x/sync/errgroup"
	"os"
	"strings"
)

type Coder struct {
	dir string
}

func NewCoder(dir string) *Coder {
	return &Coder{dir: dir}
}

func encodeString(name string) string {
	return base64.URLEncoding.EncodeToString([]byte(name))
}

func decodeString(name string) (string, error) {
	bytes, err := base64.URLEncoding.DecodeString(name)
	if err != nil {
		return name, err
	}
	return string(bytes), nil
}

func initName(name string, isDir bool) (string, error) {
	if isDir {
		return name + "$", nil
	}
	if strings.Contains(name, ".") {
		return name, nil
	}
	return name + "$", nil
}

func encodeName(name string, isDir bool) (string, error) {

	if isDir {
		if strings.HasSuffix(name, "$") {
			return encodeString(name), nil
		}
		return name, nil
	}
	if strings.Contains(name, ".") || strings.HasSuffix(name, "$") {
		return encodeString(name), nil
	}
	return name, nil
}

func decodeName(name string, isDir bool) (string, error) {

	if isDir {
		if strings.HasSuffix(name, "$") {
			return name, nil
		}
		return decodeString(name)
	}

	if strings.Contains(name, ".") || strings.HasSuffix(name, "$") {
		return name, nil
	}

	return decodeString(name)
}

func (c *Coder) Init() error {
	return c.walk(c.dir, initName)
}

func (c *Coder) Encode() error {
	return c.walk(c.dir, encodeName)
}

func (c *Coder) Decode() error {
	return c.walk(c.dir, decodeName)
}

func joinPath(dir, name string) string {
	return dir + "/" + name
}

func (c *Coder) walk(dir string, codeFunc func(string, bool) (string, error)) error {
	dirEntries, err := os.ReadDir(dir)

	if err != nil {
		return err
	}

	for _, entry := range dirEntries {
		fileName := entry.Name()
		if fileName[0] != '.' && entry.IsDir() {
			err := c.walk(joinPath(dir, fileName), codeFunc)
			if err != nil {
				return err
			}
		}
	}

	var codeGroup errgroup.Group

	for _, entry := range dirEntries {
		fileEntry := entry
		fileName := entry.Name()
		if fileName[0] != '.' {
			codeGroup.Go(func() error {
				coded, err := codeFunc(fileName, fileEntry.IsDir())

				if err != nil {
					return err
				}

				if fileName == coded {
					return nil
				}

				return os.Rename(joinPath(dir, fileName), joinPath(dir, coded))
			})
		}

	}

	return codeGroup.Wait()
}
